//
// Created by root on 6/18/17.
//

#include "../runner.h"

WRAP_SCHEME_OP2(real_add)
WRAP_SCHEME_OP2(real_mul)
WRAP_SCHEME_OP2(real_sub)
WRAP_SCHEME_OP2(real_div)

#define MOCK_REAL(_op,_p,_x,_y,_z)  Cell* _p##_arg_x = make_integer(global_scheme, _x); \
    Cell* _p##_arg_y = make_integer(global_scheme, _y); \
    Cell* _p##_ret = make_integer(global_scheme, _z); \
    expect_value(__wrap_real_##_op, sc, global_scheme); \
    expect_memory(__wrap_real_##_op, x, _p##_arg_x, sizeof(Cell)); \
    expect_memory(__wrap_real_##_op, y, _p##_arg_y, sizeof(Cell)); \
    will_return(__wrap_real_##_op, _p##_ret)

#define MOCK_REAL_DIV(_p,_x,_y) Cell* _p##_arg_x = make_integer(global_scheme, _x); \
    Cell* _p##_arg_y = make_integer(global_scheme, _y); \
    Cell* _p##_ret = make_fraction(global_scheme, make_integer(global_scheme, _x), make_integer(global_scheme, _y)); \
    expect_value(__wrap_real_div, sc, global_scheme); \
    expect_memory(__wrap_real_div, x, _p##_arg_x, sizeof(Cell)); \
    expect_memory(__wrap_real_div, y, _p##_arg_y, sizeof(Cell)); \
    will_return(__wrap_real_div, _p##_ret)

/**
 * normal cases for complex operation
 * */

/**
 * (a+bi)+(c+di) = (a+c)+(b+d)i
 * */
static void test_complex_add() {
    Cell* arg_x = make_complex(global_scheme, make_integer(global_scheme, 1), make_integer(global_scheme, 2));
    Cell* arg_y = make_complex(global_scheme, make_integer(global_scheme, 3), make_integer(global_scheme, 4));
    Cell* ret = make_complex(global_scheme, make_integer(global_scheme, 4), make_integer(global_scheme, 6));
    MOCK_REAL(add, x1, 1, 3, 4);
    MOCK_REAL(add, x2, 2, 4, 6);
    Cell* r = complex_add(global_scheme, arg_x, arg_y);
    assert_true(eqn(r, ret));
}

/**
 * (a+bi)*(c+di) = (ac-bd)+(ad+bc)i
 * */
static void test_complex_mul() {
    Cell* arg_x = make_complex(global_scheme, make_integer(global_scheme, 1), make_integer(global_scheme, 2));
    Cell* arg_y = make_complex(global_scheme, make_integer(global_scheme, 3), make_integer(global_scheme, 4));
    Cell* ret = make_complex(global_scheme, make_integer(global_scheme, -5), make_integer(global_scheme, 10));
    MOCK_REAL(mul, x1, 1, 3, 3);
    MOCK_REAL(mul, x2, 2, 4, 8);
    MOCK_REAL(sub, x3, 3, 8, -5);
    MOCK_REAL(mul, x4, 1, 4, 4);
    MOCK_REAL(mul, x5, 2, 3, 6);
    MOCK_REAL(add, x6, 4, 6, 10);
    Cell* r = complex_mul(global_scheme, arg_x, arg_y);
    assert_true(eqn(r, ret));
}

/**
 * (a+bi)-(c+di) = (a-c)+(b-d)i
 * */
static void test_complex_sub() {
    Cell* arg_x = make_complex(global_scheme, make_integer(global_scheme, 1), make_integer(global_scheme, 2));
    Cell* arg_y = make_complex(global_scheme, make_integer(global_scheme, 3), make_integer(global_scheme, 4));
    Cell* ret = make_complex(global_scheme, make_integer(global_scheme, -2), make_integer(global_scheme, -2));
    MOCK_REAL(sub, x1, 1, 3, -2);
    MOCK_REAL(sub, x2, 2, 4, -2);
    Cell* r = complex_sub(global_scheme, arg_x, arg_y);
    assert_true(eqn(r, ret));
}

/**
 * (a+bi)/(c+di) = (a+bi)(c-di)/(c^2+d^2) = (ac+bd)/(c^2+d^2)+(bc-ad)/(c^2+d^2)i
 * */
static void test_complex_div() {
    Cell* arg_x = make_complex(global_scheme, make_integer(global_scheme, 1), make_integer(global_scheme, 2));
    Cell* arg_y = make_complex(global_scheme, make_integer(global_scheme, 3), make_integer(global_scheme, 4));
    Cell* a = make_fraction(global_scheme, make_integer(global_scheme, 11), make_integer(global_scheme, 25));
    Cell* b = make_fraction(global_scheme, make_integer(global_scheme, 2), make_integer(global_scheme, 25));
    Cell* ret = make_complex(global_scheme, a, b);
    MOCK_REAL(mul, d1, 3, 3, 9);
    MOCK_REAL(mul, d2, 4, 4, 16);
    MOCK_REAL(add, denom, 9, 16, 25);
    MOCK_REAL(mul, x1, 1, 3, 3);
    MOCK_REAL(mul, x2, 2, 4, 8);
    MOCK_REAL(add, x3, 3, 8, 11);
    MOCK_REAL(mul, x4, 2, 3, 6);
    MOCK_REAL(mul, x5, 1, 4, 4);
    MOCK_REAL(sub, x6, 6, 4, 2);
    MOCK_REAL_DIV(x7, 11, 25);
    MOCK_REAL_DIV(x8, 2, 25);
    Cell* r = complex_div(global_scheme, arg_x, arg_y);
    assert_true(eqn(r, ret));
}

/**
 * cases for complex operation when result type changed
 * */

/**
 * (a+bi)+(c+di) = (a+c)+(b+d)i : b+d=0
 * */
static void test_complex_add_returns_real() {
    Cell* arg_x = make_complex(global_scheme, make_integer(global_scheme, 1), make_integer(global_scheme, 2));
    Cell* arg_y = make_complex(global_scheme, make_integer(global_scheme, 3), make_integer(global_scheme, -2));
    Cell* ret = make_integer(global_scheme, 4);
    MOCK_REAL(add, x1, 1, 3, 4);
    MOCK_REAL(add, x2, 2, -2, 0);
    Cell* r = complex_add(global_scheme, arg_x, arg_y);
    assert_true(eqn(r, ret));
}

/**
 * (a+bi)*(c+di) = (ac-bd)+(ad+bc)i : ad+bc=0
 * */
static void test_complex_mul_returns_real() {
    Cell* arg_x = make_complex(global_scheme, make_integer(global_scheme, 2), make_integer(global_scheme, 3));
    Cell* arg_y = make_complex(global_scheme, make_integer(global_scheme, 2), make_integer(global_scheme, -3));
    Cell* ret = make_integer(global_scheme, 13);
    MOCK_REAL(mul, x1, 2, 2, 4);
    MOCK_REAL(mul, x2, 3, -3, -9);
    MOCK_REAL(sub, x3, 4, -9, 13);
    MOCK_REAL(mul, x4, 2, -3, -6);
    MOCK_REAL(mul, x5, 3, 2, 6);
    MOCK_REAL(add, x6, -6, 6, 0);
    Cell* r = complex_mul(global_scheme, arg_x, arg_y);
    assert_true(eqn(r, ret));
}

/**
 * (a+bi)-(c+di) = (a-c)+(b-d)i : b-d=0
 * */
static void test_complex_sub_returns_real() {
    Cell* arg_x = make_complex(global_scheme, make_integer(global_scheme, 1), make_integer(global_scheme, 2));
    Cell* arg_y = make_complex(global_scheme, make_integer(global_scheme, 3), make_integer(global_scheme, 2));
    Cell* ret = make_integer(global_scheme, -2);
    MOCK_REAL(sub, x1, 1, 3, -2);
    MOCK_REAL(sub, x2, 2, 2, 0);
    Cell* r = complex_sub(global_scheme, arg_x, arg_y);
    assert_true(eqn(r, ret));
}

/**
 * (a+bi)/(c+di) = (a+bi)(c-di)/(c^2+d^2) = (ac+bd)/(c^2+d^2)+(bc-ad)/(c^2+d^2)i : bc-ad=0
 * */
static void test_complex_div_returns_real() {
    Cell* arg_x = make_complex(global_scheme, make_integer(global_scheme, 3), make_integer(global_scheme, 2));
    Cell* arg_y = make_complex(global_scheme, make_integer(global_scheme, 6), make_integer(global_scheme, 4));
    Cell* ret = make_fraction(global_scheme, make_integer(global_scheme, 1), make_integer(global_scheme, 2));
    MOCK_REAL(mul, d1, 6, 6, 36);
    MOCK_REAL(mul, d2, 4, 4, 16);
    MOCK_REAL(add, denom, 36, 16, 52);
    MOCK_REAL(mul, x1, 3, 6, 18);
    MOCK_REAL(mul, x2, 2, 4, 8);
    MOCK_REAL(add, x3, 18, 8, 26);
    MOCK_REAL(mul, x4, 2, 6, 12);
    MOCK_REAL(mul, x5, 3, 4, 12);
    MOCK_REAL(sub, x6, 12, 12, 0);
    MOCK_REAL_DIV(x7, 26, 52);
    MOCK_REAL_DIV(x8, 0, 52);
    Cell* r = complex_div(global_scheme, arg_x, arg_y);
    assert_true(eqn(r, ret));
}

int main(int argc, char **argv) {
    const struct CMUnitTest tests[] = {
            cmocka_unit_test(test_complex_add),
            cmocka_unit_test(test_complex_add_returns_real),
            cmocka_unit_test(test_complex_mul),
            cmocka_unit_test(test_complex_mul_returns_real),
            cmocka_unit_test(test_complex_sub),
            cmocka_unit_test(test_complex_sub_returns_real),
            cmocka_unit_test(test_complex_div),
            cmocka_unit_test(test_complex_div_returns_real),
    };
    return cmocka_run_group_tests(tests, setup_scheme, teardown_scheme);
}
